package com.github.easyms.server.service.auth.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.easyms.common.constant.AuthConstants;
import com.github.easyms.common.constant.EasymsServiceStatus;
import com.github.easyms.common.constant.GlobalConstants;
import com.github.easyms.common.entity.auth.Role;
import com.github.easyms.common.entity.auth.User;
import com.github.easyms.common.exception.EasymsException;
import com.github.easyms.common.property.EasymsProperties;
import com.github.easyms.common.util.EncryptionHelper;
import com.github.easyms.common.util.StringHelper;
import com.github.easyms.common.vo.EasymsPage;
import com.github.easyms.common.vo.auth.LoginCheckVo;
import com.github.easyms.common.vo.auth.ResetPasswordVo;
import com.github.easyms.server.config.redis.RedisService;
import com.github.easyms.server.config.shiro.JwtService;
import com.github.easyms.server.config.shiro.JwtToken;
import com.github.easyms.server.mapper.auth.UserMapper;
import com.github.easyms.server.service.auth.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.UUID;

/**
 * @author Leo.Liao
 * 用户业务接口实现类
 * @date 2020-04-09 17:06:24
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    private UserMapper userMapper;

    @Resource
    private EasymsProperties easymsProperties;

    @Resource
    private JwtService jwtService;

    @Resource
    private RedisService redisService;

    @Override
    public <E extends IPage<User>> E page(E page) {
        EasymsPage<User> easymsPage = (EasymsPage<User>) page;
        EasymsPage<User> resultPage = userMapper.selectUserPage(easymsPage, easymsPage.getCondition());
        return (E) resultPage;
    }

    @Override
    public boolean save(User entity) {
        String salt = UUID.randomUUID().toString();
        String password = EncryptionHelper.md5Encrypt(easymsProperties.getDefaultPwd(), salt);
        entity.setPassword(password);
        entity.setLanguage(easymsProperties.getDefaultLang());
        entity.setSalt(salt);
        entity.setCreateTime(LocalDateTime.now());
        return super.save(entity);
    }

    @Override
    public boolean updateById(User entity) {
        entity.setUpdateTime(LocalDateTime.now());
        return super.updateById(entity);
    }

    @Override
    public void lockingUser(String userId) {
        if(StringUtils.isEmpty(userId)){
            throw new EasymsException(EasymsServiceStatus.INVALID_PARAM);
        }
        if(null == userMapper.selectById(userId)){
            throw new EasymsException(EasymsServiceStatus.NOT_EXIST);
        }
        User user = new User();
        user.setUserId(userId);
        user.status = GlobalConstants.DataStatus.LOCKED;
        userMapper.updateById(user);
    }

    @Override
    public void updatePwdErrTimes(String userId, Integer pwdErrTimes) {
        if(StringUtils.isEmpty(userId) || null == pwdErrTimes){
            throw new EasymsException(EasymsServiceStatus.INVALID_PARAM);
        }
        if(null == userMapper.selectById(userId)){
            throw new EasymsException(EasymsServiceStatus.NOT_EXIST);
        }
        User user = new User();
        user.setUserId(userId);
        user.setPwdErrTimes(pwdErrTimes);
        userMapper.updateById(user);
    }

    @Override
    public String checkPwdStatus(String userId) {
        if(StringUtils.isEmpty(userId)){
            throw new EasymsException(EasymsServiceStatus.INVALID_PARAM);
        }
        User user = userMapper.selectById(userId);
        if(null == user){
            throw new EasymsException(EasymsServiceStatus.NOT_EXIST);
        }
        String pwdStatus = AuthConstants.PwdStatus.NORMAL;
        if (user.getPassword().equals(EncryptionHelper.md5Encrypt(easymsProperties.getDefaultPwd(), user.getSalt()))) {
            // 默认密码
            pwdStatus = AuthConstants.PwdStatus.DEFAULT;
        }else{
            // 用户上次修改密码时间 + 密码过期天数
            LocalDateTime localDateTime = user.getLastEditPwd().plusDays(easymsProperties.getPwdExpireDays());
            LocalDateTime now = LocalDateTime.now();
            if (now.compareTo(localDateTime) == 1) {
                // 密码过期
                pwdStatus = AuthConstants.PwdStatus.EXPIRE;
            }
        }
        return pwdStatus;
    }

    @Override
    public void changePassword(ResetPasswordVo resetPasswordVo) {

        // 用户ID合法性校验
        User user = userMapper.selectById(resetPasswordVo.getUserId());
        if(null == user){
            throw new EasymsException(EasymsServiceStatus.NOT_EXIST,"UserId",resetPasswordVo.getUserId());
        }

        // 上次修改密码时间检查
        LocalDateTime lastEditPwd = user.getLastEditPwd();
        if(null != lastEditPwd){
            lastEditPwd = lastEditPwd.plusDays(easymsProperties.getPwdUpdTimeInterval());
            if(lastEditPwd.compareTo(LocalDateTime.now()) == 1){
                // 修改密码过于频繁
                throw new EasymsException(EasymsServiceStatus.UPD_PWD_FREQUENTLY);
            }
        }

        // 旧密码校验
        LoginCheckVo loginCheckVo = new LoginCheckVo();
        loginCheckVo.setAccount(user.getAccount());
        loginCheckVo.setPassword(resetPasswordVo.getOldPwd());
        this.loginCheck(loginCheckVo);

        // 新密码校验
        if(EncryptionHelper.md5Encrypt(resetPasswordVo.getNewPwd(), user.getSalt()).equals(user.getPassword())){
            // 新密码不能与旧密码一样
            throw new EasymsException(EasymsServiceStatus.DUPLICATE_PWD);
        }

        // 执行更新
        user.setPassword(EncryptionHelper.md5Encrypt(resetPasswordVo.getNewPwd(), user.getSalt()));
        user.setLastEditPwd(LocalDateTime.now());
        userMapper.updateById(user);
    }

    @Override
    public void resetPassword(String userId) {
        // 用户ID合法性校验
        User user = userMapper.selectById(userId);
        if(null == user){
            throw new EasymsException(EasymsServiceStatus.NOT_EXIST,"UserId",userId);
        }
        user.setPassword(EncryptionHelper.md5Encrypt(easymsProperties.getDefaultPwd(), user.getSalt()));
        userMapper.updateById(user);
    }

    @Override
    public User loginCheck(LoginCheckVo loginCheckVo) {
        User user = this.getOne(Wrappers.<User>lambdaQuery().eq(User::getAccount,loginCheckVo.getAccount()));
        if (user == null) {
            // 账号不存在
            throw new EasymsException(EasymsServiceStatus.ACCOUNT_NOT_EXIST);
        }
        if (user.status.equals(GlobalConstants.DataStatus.LOCKED)) {
            // 账号被锁定
            throw new EasymsException(EasymsServiceStatus.ACCOUNT_LOCKED);
        }
        // 密码对比
        if (!EncryptionHelper.md5Encrypt(loginCheckVo.getPassword(), user.getSalt()).equals(user.getPassword())) {
            // 密码错误
            // 用户密码错误次数
            Integer pwdErrors = user.getPwdErrTimes();
            this.updatePwdErrTimes(user.getUserId(), ++pwdErrors);
            if (pwdErrors - easymsProperties.getMaxPwdErrs() >= 0) {
                // 超过或达到最大允许错误次数，锁定账户
                this.lockingUser(user.getUserId());
                // 账户被锁定
                throw new EasymsException(EasymsServiceStatus.ACCOUNT_LOCKED);
            }

            // 剩余可尝试次数
            Integer residualTimes = (easymsProperties.getMaxPwdErrs() - pwdErrors);
            throw new EasymsException(EasymsServiceStatus.ERR_PASSWORD,residualTimes);
        }
        // 密码错误次数重置
        this.updatePwdErrTimes(user.getUserId(),0);
        return user;
    }

    @Override
    public String createToken(String userId) {
        User user = userMapper.selectById(userId);
        if(user == null){
            throw new EasymsException(EasymsServiceStatus.NOT_EXIST);
        }
        if(StringUtils.isEmpty(user.getRoleId())){
            throw new EasymsException(EasymsServiceStatus.NO_ROLES);
        }
        String strToken = jwtService.sign(userId,user.getRoleId());

        // 单设备登陆，删除用户在其它客户端存储的token
        String oldToken = (String) redisService.get(userId);
        if (!StringUtils.isEmpty(oldToken)){
            redisService.deleteKey(oldToken);
            redisService.deleteKey(userId);
        }
        // 存储用户新的token
        redisService.set(userId , strToken , easymsProperties.getTokenExpireTime());
        redisService.set(strToken , userId , easymsProperties.getTokenExpireTime());

        // 执行shiro登陆认证
        Subject subject = SecurityUtils.getSubject();
        AuthenticationToken token = new JwtToken(strToken);
        subject.login(token);

        return strToken;
    }

    @Override
    public Boolean deleteToken(String userId) {
        String oldToken = (String) redisService.get(userId);
        if (!StringUtils.isEmpty(oldToken)){
            redisService.deleteKey(oldToken);
            redisService.deleteKey(userId);
        }
        return true;
    }

    @Override
    public User getActiveUserById(String userId) {
        User user = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                .eq(User::getStatus, GlobalConstants.DataStatus.ACTIVE)
                .eq(User::getUserId, userId)
        );
        return user;
    }

    @Override
    public Boolean checkAccount(String userId,String account) {

        User user = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                .eq(User::getAccount, account)
                .last("limit 1")
        );

        if(!StringHelper.isEmpty(userId)){
            // 如果用户ID不为空，则查询处此用户accout之外的数据是否重复
            User updateUser = userMapper.selectById(userId);
            if(account.equals(updateUser.getAccount())){
                return true;
            }else{
                // 若查询结果为null，则说明该账号未被使用，返回true
                if(user == null){
                    return true;
                }else{
                    // 若查询结果不为null，则说明该账号已被使用，
                    return false;
                }
            }
        }else{
            // 若查询结果为null，则说明该账号未被使用，返回true
            if(user == null){
                return true;
            }else{
                // 若查询结果不为null，则说明该账号已被使用，
                return false;
            }
        }
    }
}
